/**
 * 
 */
package com.jolestar.yayaweibotong.dao;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.jolestar.yayaweibotong.ServiceType;
import com.jolestar.yayaweibotong.YayaException;

/**
 * @author jolestar@gmail.com
 * 
 */
public class DomainFactory {

	private static DatastoreService datastore = DatastoreServiceFactory
			.getDatastoreService();
	private static final ConcurrentHashMap<String, AtomicLong> idGenerators = new ConcurrentHashMap<String, AtomicLong>();
	private static final Logger log = Logger.getLogger(DomainFactory.class
			.getName());
	private static Entity idGenerator;
	static {
		Key idGeneratorKey = KeyFactory.createKey("System", "idGenerator");
		try {
			idGenerator = datastore.get(idGeneratorKey);
			Map<String, Object> properties = idGenerator.getProperties();
			for (Entry<String, Object> e : properties.entrySet()) {
				idGenerators.put(e.getKey(),
						new AtomicLong((Long) e.getValue()));
			}
		} catch (EntityNotFoundException e) {
			idGenerator = new Entity(idGeneratorKey);
			datastore.put(idGenerator);
		}
	}

	public static DatastoreService getDatastore() {
		return datastore;
	}

	public static long generateId(String kind) {
		AtomicLong newGen = new AtomicLong(0);
		AtomicLong generator = idGenerators.putIfAbsent(kind, newGen);
		if (generator == null) {
			generator = newGen;
		}
		long result = generator.getAndIncrement();
		synchronized (kind) {
			idGenerator.setProperty(kind, result);
			datastore.put(idGenerator);
		}
		return result;
	}

	static <T extends AbstractDomain> T load(Key key) {
		if (key == null) {
			return null;
		}
		try {
			Entity entity = datastore.get(key);
			return init(entity);
		} catch (EntityNotFoundException e) {
			return null;
		}
	}

	public static <T extends AbstractDomain> T load(Class<T> clazz,
			ServiceType serviceType, String originalId) {
		return load(clazz, generateId(serviceType, originalId));
	}

	public static <T extends AbstractDomain> T load(Class<T> clazz,
			String keyName) {
		return load(KeyFactory.createKey(clazz.getName(), keyName));
	}

   static boolean contains(Key key) {
		try {
			datastore.get(key);
			return true;
		} catch (EntityNotFoundException e) {
			return false;
		}
	}

	public static boolean contains(Class clazz, ServiceType serviceType,
			String originalId) {
		return contains(KeyFactory.createKey(clazz.getName(),
				generateId(serviceType, originalId)));
	}

	public static <T extends AbstractDomain> Iterable<T> getAllByQuery(
			Query query) {
		return new DomainIterable<T>(datastore.prepare(query).asIterator());
	}

	public static <T extends AbstractDomain> Iterable<T> getAllByQuery(
			Query query, FetchOptions option) {
		return new DomainIterable<T>(datastore.prepare(query)
				.asIterator(option));
	}

	public static <T extends AbstractDomain> List<T> getListByQuery(Query query) {
		return getListByQuery(query, FetchOptions.Builder.withDefaults());
	}

	public static <T extends AbstractDomain> List<T> getListByQuery(
			Query query, FetchOptions option) {
		return Lists.newArrayList(DomainFactory
				.<T> getAllByQuery(query, option));
	}

	public static void delete(Key key) {
		datastore.delete(key);
	}

	private static <T> T init(Entity entity) {
		Key key = entity.getKey();
		String kind = key.getKind();
		try {
			Class domainClass = Class.forName(kind);
			Constructor constructor = domainClass.getConstructor(Entity.class);
			Object obj = constructor.newInstance(entity);
			return (T) obj;
		} catch (ClassNotFoundException e) {
			throw new YayaException("can not find class:" + kind);
		} catch (SecurityException e) {
			throw new YayaException(e);
		} catch (NoSuchMethodException e) {
			throw new YayaException(
					"can not find constructor with entity paramerter.");
		} catch (IllegalArgumentException e) {
			throw new YayaException(e);
		} catch (InstantiationException e) {
			throw new YayaException(e);
		} catch (IllegalAccessException e) {
			throw new YayaException(e);
		} catch (InvocationTargetException e) {
			throw new YayaException(e);
		}
	}

	public static class DomainIterable<T extends AbstractDomain> implements
			Iterable<T> {
		private Iterator<Entity> entities;
		private Entity entity;

		DomainIterable(Iterator<Entity> entities) {
			this.entities = entities;
		}

		@Override
		public Iterator<T> iterator() {
			return new DomainIterator<T>(entities);
		}
	}

	public static class DomainIterator<T extends AbstractDomain> implements
			Iterator<T> {

		private Iterator<Entity> entities;
		private Entity entity;

		DomainIterator(Iterator<Entity> entities) {
			this.entities = entities;
		}

		@Override
		public boolean hasNext() {
			return this.entities.hasNext();
		}

		@Override
		public T next() {
			this.entity = this.entities.next();
			return init(this.entity);
		}

		@Override
		public void remove() {
			datastore.delete(this.entity.getKey());
		}
	}

	public static String generateId(ServiceType serviceType, String originalId) {
		return String.format(DomainFactory.ID_FORMAT, originalId,
				serviceType.getHost());
	}

	public static final String ID_FORMAT = "%s@%s";

}
